--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Node / ReticulumProjects / MeshChatX.git / files / tests / backend / module_ownership_contract_helpers.py
Displaying Raw • Download
tests/backend/module_ownership_contract_helpers.py dev (af76f09d) Text, 4.87 KB
T8b949e# SPDX-License-Identifier: 0BSD
T8b949e"""Helpers for the backend module ownership contract.
Parses the "Backend" table in docs/agents/module-ownership.md into a plain
row structure, and resolves the backtick-quoted spans in that table to
on-disk paths under meshchatx/src/backend/ or
meshchatx/src/frontend/components/. Prose cells without backtick spans
(for example "(none)" or "App shell banners") resolve to an empty list,
since they describe behaviour rather than referencing a concrete file.
"""
Tff7b72from T7ee787__future__ Tff7b72import Te6edf3annotations
Tff7b72import T7ee787json
Tff7b72import T7ee787re
Tff7b72from T7ee787pathlib Tff7b72import Te6edf3Path
Te6edf3BACKEND_ROOT Tff7b72= Ta5d6ff"Ta5d6ffmeshchatx/src/backendTa5d6ff"
Te6edf3FRONTEND_COMPONENTS_ROOT Tff7b72= Ta5d6ff"Ta5d6ffmeshchatx/src/frontend/componentsTa5d6ff"
Te6edf3_HEADER_KEY_MAP Tff7b72= Tb4b4b4{
Ta5d6ff"Ta5d6ffdomainTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffdomainTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffmanager modulesTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffmanager_modulesTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffhttp route moduleTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffhttp_route_moduleTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffws moduleTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffws_moduleTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffprimary testsTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffprimary_testsTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fffrontend pageTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6fffrontend_pageTa5d6ff"Tb4b4b4,
Tb4b4b4}
Te6edf3_CODE_SPAN_RE Tff7b72= Te6edf3reTff7b72.Td2a8ffcompileTb4b4b4(Ta5d6ffrTa5d6ff"Ta5d6ff`([^`]+)`Ta5d6ff"Tb4b4b4)
Tff7b72def Td2a8ff_split_rowTb4b4b4(Te6edf3lineTb4b4b4: Tffa657strTb4b4b4) Tff7b72-Tff7b72> Tffa657listTb4b4b4[Tffa657strTb4b4b4]Tb4b4b4:
Te6edf3cells Tff7b72= Te6edf3lineTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tff7b72.Td2a8ffsplitTb4b4b4(Ta5d6ff"Ta5d6ff|Ta5d6ff"Tb4b4b4)
Tff7b72if Te6edf3cells Tff7b72and Te6edf3cellsTb4b4b4[T79c0ff0Tb4b4b4]Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4) Tff7b72== Ta5d6ff"Ta5d6ff"Tb4b4b4:
Te6edf3cells Tff7b72= Te6edf3cellsTb4b4b4[T79c0ff1Tb4b4b4:Tb4b4b4]
Tff7b72if Te6edf3cells Tff7b72and Te6edf3cellsTb4b4b4[Tff7b72-T79c0ff1Tb4b4b4]Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4) Tff7b72== Ta5d6ff"Ta5d6ff"Tb4b4b4:
Te6edf3cells Tff7b72= Te6edf3cellsTb4b4b4[Tb4b4b4:Tff7b72-T79c0ff1Tb4b4b4]
Tff7b72return Tb4b4b4[Te6edf3cTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4) Tff7b72for Te6edf3c Tff7b72in Te6edf3cellsTb4b4b4]
Tff7b72def Td2a8ff_code_spansTb4b4b4(Te6edf3cellTb4b4b4: Tffa657strTb4b4b4) Tff7b72-Tff7b72> Tffa657listTb4b4b4[Tffa657strTb4b4b4]Tb4b4b4:
Tff7b72return Te6edf3_CODE_SPAN_RETff7b72.Td2a8fffindallTb4b4b4(Te6edf3cellTb4b4b4)
Tff7b72def Td2a8ffparse_backend_ownership_tableTb4b4b4(Te6edf3doc_pathTb4b4b4: Te6edf3PathTb4b4b4) Tff7b72-Tff7b72> Tffa657listTb4b4b4[Tffa657dictTb4b4b4]Tb4b4b4:
T8b949e"""Parse the Backend markdown table into a list of row dicts.
Each row maps header keys to lists of backtick-quoted spans, except
"domain" which maps to the plain cell text. Only the pipe table found
directly under the "## Backend" heading is parsed.
"""
Te6edf3lines Tff7b72= Te6edf3doc_pathTff7b72.Td2a8ffread_textTb4b4b4(Te6edf3encodingTff7b72=Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)Tff7b72.Td2a8ffsplitlinesTb4b4b4(Tb4b4b4)
Te6edf3start Tff7b72= Tff7b72None
Tff7b72for Te6edf3iTb4b4b4, Te6edf3line Tff7b72in Tffa657enumerateTb4b4b4(Te6edf3linesTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3lineTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4) Tff7b72== Ta5d6ff"Ta5d6ff## BackendTa5d6ff"Tb4b4b4:
Te6edf3start Tff7b72= Te6edf3i Tff7b72+ T79c0ff1
Tff7b72break
Tff7b72if Te6edf3start Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72raise Tf85149ValueErrorTb4b4b4(Ta5d6ff"Ta5d6ffBackend section heading not found in module-ownership.mdTa5d6ff"Tb4b4b4)
Te6edf3end Tff7b72= Tffa657lenTb4b4b4(Te6edf3linesTb4b4b4)
Tff7b72for Te6edf3i Tff7b72in Tffa657rangeTb4b4b4(Te6edf3startTb4b4b4, Tffa657lenTb4b4b4(Te6edf3linesTb4b4b4)Tb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3linesTb4b4b4[Te6edf3iTb4b4b4]Tff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ff"Ta5d6ff## Ta5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3end Tff7b72= Te6edf3i
Tff7b72break
Te6edf3table_lines Tff7b72= Tb4b4b4[Te6edf3line Tff7b72for Te6edf3line Tff7b72in Te6edf3linesTb4b4b4[Te6edf3startTb4b4b4:Te6edf3endTb4b4b4] Tff7b72if Te6edf3lineTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ff"Ta5d6ff|Ta5d6ff"Tb4b4b4)Tb4b4b4]
Tff7b72if Tffa657lenTb4b4b4(Te6edf3table_linesTb4b4b4) Tff7b72< T79c0ff2Tb4b4b4:
Tff7b72raise Tf85149ValueErrorTb4b4b4(Ta5d6ff"Ta5d6ffBackend table not found under the Backend headingTa5d6ff"Tb4b4b4)
Te6edf3header Tff7b72= Tb4b4b4[Te6edf3hTff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4) Tff7b72for Te6edf3h Tff7b72in Te6edf3_split_rowTb4b4b4(Te6edf3table_linesTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4)Tb4b4b4]
Te6edf3keys Tff7b72= Tb4b4b4[Te6edf3_HEADER_KEY_MAPTb4b4b4[Te6edf3hTb4b4b4] Tff7b72for Te6edf3h Tff7b72in Te6edf3headerTb4b4b4]
Te6edf3rows Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72for Te6edf3line Tff7b72in Te6edf3table_linesTb4b4b4[T79c0ff2Tb4b4b4:Tb4b4b4]Tb4b4b4:
Te6edf3cells Tff7b72= Te6edf3_split_rowTb4b4b4(Te6edf3lineTb4b4b4)
Te6edf3row Tff7b72= Tb4b4b4{Tb4b4b4}
Tff7b72for Te6edf3keyTb4b4b4, Te6edf3cell Tff7b72in Tffa657zipTb4b4b4(Te6edf3keysTb4b4b4, Te6edf3cellsTb4b4b4)Tb4b4b4:
Te6edf3rowTb4b4b4[Te6edf3keyTb4b4b4] Tff7b72= Te6edf3cell Tff7b72if Te6edf3key Tff7b72== Ta5d6ff"Ta5d6ffdomainTa5d6ff" Tff7b72else Te6edf3_code_spansTb4b4b4(Te6edf3cellTb4b4b4)
Te6edf3rowsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3rowTb4b4b4)
Tff7b72return Te6edf3rows
Tff7b72def Td2a8ffwrite_ownership_fixtureTb4b4b4(Te6edf3fixture_pathTb4b4b4: Te6edf3PathTb4b4b4, Te6edf3rowsTb4b4b4: Tffa657listTb4b4b4[Tffa657dictTb4b4b4]Tb4b4b4) Tff7b72-Tff7b72> Tff7b72NoneTb4b4b4:
Te6edf3payload Tff7b72= Tb4b4b4{Ta5d6ff"Ta5d6ffversionTa5d6ff"Tb4b4b4: T79c0ff1Tb4b4b4, Ta5d6ff"Ta5d6ffdomainsTa5d6ff"Tb4b4b4: Te6edf3rowsTb4b4b4}
Te6edf3fixture_pathTff7b72.Td2a8ffwrite_textTb4b4b4(Te6edf3jsonTff7b72.Td2a8ffdumpsTb4b4b4(Te6edf3payloadTb4b4b4, Te6edf3indentTff7b72=T79c0ff4Tb4b4b4) Tff7b72+ Ta5d6ff"Tffea00\nTa5d6ff"Tb4b4b4, Te6edf3encodingTff7b72=Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)
Tff7b72def Td2a8ffload_ownership_fixtureTb4b4b4(Te6edf3fixture_pathTb4b4b4: Te6edf3PathTb4b4b4) Tff7b72-Tff7b72> Tffa657listTb4b4b4[Tffa657dictTb4b4b4]Tb4b4b4:
Te6edf3payload Tff7b72= Te6edf3jsonTff7b72.Td2a8ffloadsTb4b4b4(Te6edf3fixture_pathTff7b72.Td2a8ffread_textTb4b4b4(Te6edf3encodingTff7b72=Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)Tb4b4b4)
Tff7b72return Te6edf3payloadTb4b4b4[Ta5d6ff"Ta5d6ffdomainsTa5d6ff"Tb4b4b4]
Tff7b72def Td2a8ff_resolve_spansTb4b4b4(
Te6edf3spansTb4b4b4: Tffa657listTb4b4b4[Tffa657strTb4b4b4]Tb4b4b4,
Te6edf3rootTb4b4b4: Tffa657strTb4b4b4,
Te6edf3default_dirTb4b4b4: Tffa657str Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4,
Te6edf3carry_forwardTb4b4b4: Tffa657bool Tff7b72= Tff7b72FalseTb4b4b4,
Tb4b4b4) Tff7b72-Tff7b72> Tffa657listTb4b4b4[Tffa657tupleTb4b4b4[Tffa657strTb4b4b4, Tffa657strTb4b4b4]Tb4b4b4]Tb4b4b4:
T8b949e"""Resolve backtick spans to (relative_path, kind) pairs, kind in {file, dir}.
A span ending in "/*" or "/" is a directory reference. A span containing
"/" is a fully qualified path and updates the carried directory. A bare
filename resolves against the carried directory when carry_forward is
set, otherwise against default_dir.
"""
Te6edf3resolvedTb4b4b4: Tffa657listTb4b4b4[Tffa657tupleTb4b4b4[Tffa657strTb4b4b4, Tffa657strTb4b4b4]Tb4b4b4] Tff7b72= Tb4b4b4[Tb4b4b4]
Te6edf3current_dir Tff7b72= Te6edf3default_dir
Tff7b72for Te6edf3span Tff7b72in Te6edf3spansTb4b4b4:
Tff7b72if Te6edf3spanTff7b72.Td2a8ffendswithTb4b4b4(Ta5d6ff"Ta5d6ff/*Ta5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3rel_dir Tff7b72= Te6edf3spanTb4b4b4[Tb4b4b4:Tff7b72-T79c0ff2Tb4b4b4]
Te6edf3resolvedTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4(Ta5d6fffTa5d6ff"Tffd700{Te6edf3rootTffd700}Ta5d6ff/Tffd700{Te6edf3rel_dirTffd700}Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffdirTa5d6ff"Tb4b4b4)Tb4b4b4)
Tff7b72continue
Tff7b72if Te6edf3spanTff7b72.Td2a8ffendswithTb4b4b4(Ta5d6ff"Ta5d6ff/Ta5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3rel_dir Tff7b72= Te6edf3spanTb4b4b4[Tb4b4b4:Tff7b72-T79c0ff1Tb4b4b4]
Te6edf3base Tff7b72= Ta5d6fffTa5d6ff"Tffd700{Te6edf3rootTffd700}Ta5d6ff/Tffd700{Te6edf3current_dirTffd700}Ta5d6ff" Tff7b72if Te6edf3current_dir Tff7b72else Te6edf3root
Te6edf3resolvedTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4(Ta5d6fffTa5d6ff"Tffd700{Te6edf3baseTffd700}Ta5d6ff/Tffd700{Te6edf3rel_dirTffd700}Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffdirTa5d6ff"Tb4b4b4)Tb4b4b4)
Tff7b72continue
Tff7b72if Ta5d6ff"Ta5d6ff/Ta5d6ff" Tff7b72in Te6edf3spanTb4b4b4:
Te6edf3current_dir Tff7b72= Te6edf3spanTff7b72.Td2a8ffrsplitTb4b4b4(Ta5d6ff"Ta5d6ff/Ta5d6ff"Tb4b4b4, T79c0ff1Tb4b4b4)Tb4b4b4[T79c0ff0Tb4b4b4]
Te6edf3resolvedTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4(Ta5d6fffTa5d6ff"Tffd700{Te6edf3rootTffd700}Ta5d6ff/Tffd700{Te6edf3spanTffd700}Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6fffileTa5d6ff"Tb4b4b4)Tb4b4b4)
Tff7b72continue
Te6edf3base_dir Tff7b72= Te6edf3current_dir Tff7b72if Te6edf3carry_forward Tff7b72else Te6edf3default_dir
Te6edf3rel Tff7b72= Ta5d6fffTa5d6ff"Tffd700{Te6edf3base_dirTffd700}Ta5d6ff/Tffd700{Te6edf3spanTffd700}Ta5d6ff" Tff7b72if Te6edf3base_dir Tff7b72else Te6edf3span
Te6edf3resolvedTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4(Ta5d6fffTa5d6ff"Tffd700{Te6edf3rootTffd700}Ta5d6ff/Tffd700{Te6edf3relTffd700}Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6fffileTa5d6ff"Tb4b4b4)Tb4b4b4)
Tff7b72return Te6edf3resolved
Tff7b72def Td2a8ffresolve_manager_module_pathsTb4b4b4(Te6edf3spansTb4b4b4: Tffa657listTb4b4b4[Tffa657strTb4b4b4]Tb4b4b4) Tff7b72-Tff7b72> Tffa657listTb4b4b4[Tffa657tupleTb4b4b4[Tffa657strTb4b4b4, Tffa657strTb4b4b4]Tb4b4b4]Tb4b4b4:
Tff7b72return Te6edf3_resolve_spansTb4b4b4(Te6edf3spansTb4b4b4, Te6edf3BACKEND_ROOTTb4b4b4)
Tff7b72def Td2a8ffresolve_http_route_pathsTb4b4b4(Te6edf3spansTb4b4b4: Tffa657listTb4b4b4[Tffa657strTb4b4b4]Tb4b4b4) Tff7b72-Tff7b72> Tffa657listTb4b4b4[Tffa657tupleTb4b4b4[Tffa657strTb4b4b4, Tffa657strTb4b4b4]Tb4b4b4]Tb4b4b4:
Tff7b72return Te6edf3_resolve_spansTb4b4b4(Te6edf3spansTb4b4b4, Te6edf3BACKEND_ROOTTb4b4b4, Te6edf3default_dirTff7b72=Ta5d6ff"Ta5d6ffhttp/routesTa5d6ff"Tb4b4b4)
Tff7b72def Td2a8ffresolve_ws_module_pathsTb4b4b4(Te6edf3spansTb4b4b4: Tffa657listTb4b4b4[Tffa657strTb4b4b4]Tb4b4b4) Tff7b72-Tff7b72> Tffa657listTb4b4b4[Tffa657tupleTb4b4b4[Tffa657strTb4b4b4, Tffa657strTb4b4b4]Tb4b4b4]Tb4b4b4:
Tff7b72return Te6edf3_resolve_spansTb4b4b4(Te6edf3spansTb4b4b4, Te6edf3BACKEND_ROOTTb4b4b4)
Tff7b72def Td2a8ffresolve_frontend_page_pathsTb4b4b4(Te6edf3spansTb4b4b4: Tffa657listTb4b4b4[Tffa657strTb4b4b4]Tb4b4b4) Tff7b72-Tff7b72> Tffa657listTb4b4b4[Tffa657tupleTb4b4b4[Tffa657strTb4b4b4, Tffa657strTb4b4b4]Tb4b4b4]Tb4b4b4:
Tff7b72return Te6edf3_resolve_spansTb4b4b4(Te6edf3spansTb4b4b4, Te6edf3FRONTEND_COMPONENTS_ROOTTb4b4b4, Te6edf3carry_forwardTff7b72=Tff7b72TrueTb4b4b4)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────